package gazzera.node.sslserver;

import gazzera.utils.*;

import java.io.*;
import java.net.*;
import java.security.*;
import java.security.cert.Certificate;

import javax.net.ssl.*;


public class Server implements Runnable
{
    SSLContext context = null;
    SSLServerSocket serverSocket = null;
    ConnectionListener listener = null;
    String hash;
    int port;
    private AcceptorTrustManager acceptor;
    private boolean hasOrderedClose = false;
    
    public Server(int port, File keystore, char[] password, CertificateAcceptor acceptor, ConnectionListener listener) throws ServerException
    {

	this.port = port;

	// TODO: to generate key: keytool -genkey -alias ssltestserver -keystore name
	try
	{
	    this.listener = listener;
	    context = SSLContext.getInstance("SSL");
	    KeyManagerFactory kmf = KeyManagerFactory.getInstance("SunX509");
	    KeyStore ks = KeyStore.getInstance("JKS");

	    FileInputStream fis = new FileInputStream(keystore);
	    ks.load(fis, password);
	    fis.close();
	    /*
	     * java.security.cert.Certificate c =
	     * ks.getCertificate("ssltestserver");
	     * 
	     * String md5 = SSLUtils.getMd5Cert(c);
	     * System.out.println("OUR MD5: " + md5 );
	     */

	    kmf.init(ks, password);
	    Certificate x = ks.getCertificate("ssltestserver");
	    hash = SSLUtils.getSha1Cert(x);
	    this.acceptor = new AcceptorTrustManager(acceptor);
	    context.init(kmf.getKeyManagers(), new TrustManager[] { this.acceptor }, null);

	    SSLServerSocketFactory factory = context.getServerSocketFactory();

	    serverSocket = (SSLServerSocket) factory.createServerSocket(port);
	    serverSocket.setNeedClientAuth(true);
	} catch (Exception e)
	{
	    throw new ServerException("Unable to start server", e);
	}
    }

    public int getPort()
    {
        return port;
    }

   
    
    private SSLSocket doconnect(String host, int port) throws ServerException
    {
	try
	{
	    SSLSocketFactory sf = context.getSocketFactory();
	    SSLSocket ss = (SSLSocket) sf.createSocket(host, port);
	    
	    try
	    {
        	    acceptor.currentsocket.put(Thread.currentThread(), ss);
        	    
        	    // force authentication, this calls AcceptorTrustManager.check etc etc
        	    
        	    //acceptor.currentsocket = ss;
        	    ss.setNeedClientAuth(true);
        	    // java.security.cert.Certificate[] cc =
        	    ss.getSession().getPeerCertificates();
    	    }
	    finally
	    {
		acceptor.currentsocket.remove(Thread.currentThread());
	    }
	    return ss;

	} catch (Exception e)
	{
	    throw new ServerException("Unable to connect", e);
	}
    }

    public boolean connect(String host, int port)
    {
    	return connect(host, port, null);
    }
    public boolean connect(String host, int port, Object o)
    {
	try
	{
	    final SSLSocket s = doconnect(host, port);
	    boolean res = listener.acceptConnection(s, o);
	    if (!res)
	    {
	    	s.close();
	    }

	    return res;

	} catch (Exception e)
	{
	    // no connection
	    return false;
	}
    }

    public Thread runAsThread()
    {
	Thread t = new Thread(this, "SSL Server");
	t.setDaemon(true);
	t.start();
	return t;
    }
    
    public void close()
    {
	try
	{
	    hasOrderedClose = true;
	    serverSocket.close();
	} 
	catch (IOException e)
	{
	    e.printStackTrace();
	}
    }

    public void run()
    {
	try
	{
	    start();
	} 
	catch (ServerException e)
	{
	    e.printStackTrace();
	}

    }

    public String getHash()
    {
	return hash;
    }

    public void start() throws ServerException
    {
	try
	{
	    while (true)
	    {
		SSLSocket s = (SSLSocket) serverSocket.accept();
		try
		{
		    
		    try
		    {
        		    acceptor.currentsocket.put(Thread.currentThread(), s);
        		    // force authentication, this calls AcceptorTrustManager.check etc etc
        		    // java.security.cert.Certificate[] cc =
        		    s.getSession().getPeerCertificates();
		    }
		    finally
		    {
					acceptor.currentsocket.remove(Thread.currentThread());
		    }
		    listener.acceptConnection(s, null);
		    
		} catch (SSLPeerUnverifiedException e)
		{
		    // peer discarded
		}
	    }
	} 
	catch (SocketException e)
	{
	    if(!hasOrderedClose) 
	    {
		throw new ServerException("Error running server", e);
	    }
	}
	catch (Exception e)
	{
	    throw new ServerException("Error running server", e);
	}
    }
}
